package com.jotta.client.zoom;

import java.util.ArrayList;
import java.util.Calendar;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;

import com.jotta.client.ModeOfSwipe;
import com.jotta.client.event.SwipeEvent;
import com.jotta.client.event.SwipeEventListener;

public class TouchImageView extends ImageView {

	private static final String TAG = "Touch";
	private static final int SWIPE_THRESHOLD_VELOCITY = 1;
	private int viewWidth = 0;
	private int viewHeight = 0;
	private RectF boundRect = new RectF();

	private ArrayList<SwipeEventListener> swipeListener = new ArrayList<SwipeEventListener>();

	public synchronized void addSwipeEventListener(SwipeEventListener listener) {
		swipeListener.add(listener);
	}

	public synchronized void removeEventListener(SwipeEventListener listener) {
		swipeListener.remove(listener);
	}

	private synchronized void fireSwipeEvent(ModeOfSwipe modeOfSwipe) {
		SwipeEvent event = new SwipeEvent(modeOfSwipe);
		for (int i = 0; i < swipeListener.size(); i++) {
			swipeListener.get(i).handleSwipeEvent(event);
		}
	}

	// These matrices will be used to move and zoom image
	Matrix matrix = new Matrix();
	Matrix savedMatrix = new Matrix();

	// We can be in one of these 3 states
	static final int NONE = 0;
	static final int DRAG = 1;
	static final int ZOOM = 2;
	int mode = NONE;

	// Remember some things for zooming
	PointF start = new PointF();
	PointF mid = new PointF();

	// Remember some things for timing
	long startTime;

	float oldDist = 1f;

	Context context;

	/**
	 * Constructor
	 */
	public TouchImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.context = context;
		initialize();
	}

	public TouchImageView(Context context) {
		super(context);
		this.context = context;
		initialize();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see android.view.View#onSizeChanged(int, int, int, int)
	 */
	@Override
	protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld) {
		super.onSizeChanged(xNew, yNew, xOld, yOld);
		viewWidth = xNew;
		viewHeight = yNew;
		reloadImage();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see android.widget.ImageView#onDraw(android.graphics.Canvas)
	 */
	@Override
	protected void onDraw(Canvas canvas) {
		Bitmap bm = getImage();
		if (bm != null && !bm.isRecycled()) {
			super.onDraw(canvas);
		}
	}

	private void initialize() {
		super.setClickable(true);

		matrix.setTranslate(1f, 1f);
		setImageMatrix(matrix);
		setScaleType(ScaleType.MATRIX);

		setOnTouchListener(new OnTouchListener() {

			@Override
			public boolean onTouch(View v, MotionEvent rawEvent) {
				Bitmap bm = getImage();
				if (bm != null && !bm.isRecycled()) {
					WrapMotionEvent event = WrapMotionEvent.wrap(rawEvent);

					// Handle touch events here...
					switch (event.getAction() & MotionEvent.ACTION_MASK) {
					case MotionEvent.ACTION_DOWN:
						savedMatrix.set(matrix);
						start.set(event.getX(), event.getY());
						mode = DRAG;
						startTime = Calendar.getInstance().getTimeInMillis();

						break;
					case MotionEvent.ACTION_POINTER_DOWN:
						oldDist = spacing(event);
						//Log.d(TAG, "oldDist=" + oldDist);
						if (oldDist > 10f) {
							savedMatrix.set(matrix);
							midPoint(mid, event);
							mode = ZOOM;
						}
						break;
					case MotionEvent.ACTION_UP:
						float xDiff = event.getX() - start.x;
						float yDiff = event.getY() - start.y;
						if (Math.abs(xDiff) < 8 && Math.abs(yDiff) < 8) {
							performClick();
						} else {
							long endTime = Calendar.getInstance()
									.getTimeInMillis();
							PointF endPoint = new PointF(event.getX(),
									event.getY());
							float velocity = calculateVelocity(start, endPoint,
									startTime, endTime);
							if (velocity > SWIPE_THRESHOLD_VELOCITY) {
								if (xDiff < 0)
									fireSwipeEvent(ModeOfSwipe.BACK);
								else
									fireSwipeEvent(ModeOfSwipe.NEXT);
							}
						}
					case MotionEvent.ACTION_POINTER_UP:
						mode = NONE;
						break;
					case MotionEvent.ACTION_MOVE:
						if (mode == DRAG) {
							// ...
							matrix.set(savedMatrix);
							matrix.postTranslate(event.getX() - start.x,
									event.getY() - start.y);

						} else if (mode == ZOOM) {
							float newDist = spacing(event);
							if (newDist > 10f) {
								matrix.set(savedMatrix);
								float scale = newDist / oldDist;
								matrix.postScale(scale, scale, mid.x, mid.y);
							}
						}
						break;
					}
					RectF reBoundRect = new RectF();
					matrix.mapRect(reBoundRect, boundRect);
					if (reBoundRect.bottom < 0 || reBoundRect.right < 0
							|| reBoundRect.top > viewHeight
							|| reBoundRect.left > viewWidth) {
						matrix.set(savedMatrix);
					} else {
						setImageMatrix(matrix);
					}
				}
				return true;
			}
		});
	}

	public void reloadImage() {
		setImage(this.getImage());
	}

	public void setImage(Bitmap bm) {
		if (bm != null) {
			setImage(bm, viewWidth, viewHeight);
		}
	}

	public void setImage(Bitmap bm, int displayWidth, int displayHeight) {
		super.setImageBitmap(bm);
		matrix.reset();
		savedMatrix.reset();

		setImageMatrix(matrix);
		boundRect.set(0, 0, bm.getWidth(), bm.getHeight());

		// Fit to screen.
		float scale;
		if ((displayHeight / bm.getHeight()) >= (displayWidth / bm.getWidth())) {
			scale = (float) displayWidth / (float) bm.getWidth();
		} else {
			scale = (float) displayHeight / (float) bm.getHeight();
		}

		savedMatrix.set(matrix);
		matrix.set(savedMatrix);
		matrix.postScale(scale, scale, 0, 0);
		setImageMatrix(matrix);

		// Center the image
		float redundantYSpace = (float) displayHeight
				- (scale * (float) bm.getHeight());
		float redundantXSpace = (float) displayWidth
				- (scale * (float) bm.getWidth());

		redundantYSpace /= (float) 2;
		redundantXSpace /= (float) 2;

		savedMatrix.set(matrix);
		matrix.set(savedMatrix);
		matrix.postTranslate(redundantXSpace, redundantYSpace);
		setImageMatrix(matrix);
	}

	public Bitmap getImage() {
		BitmapDrawable bitmapDrawable = (BitmapDrawable) getDrawable();
		if (bitmapDrawable != null)
			return bitmapDrawable.getBitmap();
		return null;
	}

	/** Determine the space between the first two fingers */
	private float spacing(WrapMotionEvent event) {
		// ...
		float x = event.getX(0) - event.getX(1);
		float y = event.getY(0) - event.getY(1);
		return FloatMath.sqrt(x * x + y * y);
	}

	/** Calculate the mid point of the first two fingers */
	private void midPoint(PointF point, WrapMotionEvent event) {
		// ...
		float x = event.getX(0) + event.getX(1);
		float y = event.getY(0) + event.getY(1);
		point.set(x / 2, y / 2);
	}

	private float calculateVelocity(PointF startPoint, PointF endPoint,
			long startTime, long endTime) {
		float x = startPoint.x - endPoint.x;
		float y = endPoint.y - endPoint.y;
		float distance = FloatMath.sqrt(x * x + y * y);
		long time = endTime - startTime;
		if (time <= 0)
			return 0;
		else
			return distance / time;
	}
}